// Georgia Tech HPC Class Project - MPI
// Jack Chua

#include "mpi-allr.h"
#include <string.h>

// allreduce function
int gthpc_Allreduce(void *sbuf, void *rbuf, int count, MPI_Op op, MPI_Comm comm)
{
  // Algorithm 4.5: All-to-all reduction on a p-node ring
  // initialize vars
  int i;
  int left, right;                   //neighbors of current node
  int size, rank;                    //size of ring
  double *temp, *recv;               //temporary buffer of doubles
  double *sbuf_d = (double*)sbuf;    //recast
  double *rbuf_d = (double*)rbuf;

  // initialize MPI vars
  MPI_Status status;
  MPI_Request request;
  MPI_Comm_size(comm, &size);
  MPI_Comm_rank(comm, &rank);
  left = modulo(rank-1,size);
  right = modulo(rank+1,size);

#ifdef DEBUG
  printf("Processor %d initialized. Left: %d, Right: %d\n", rank, left, right);
  fflush(stdout);
#endif

  // initialize temp and recv message buffers to 0 (these get passed around)
  temp = (double*)malloc(count*sizeof(double));
  recv = (double*)malloc(count*sizeof(double));
  for (i=0; i<count; i++) { temp[i]=0; recv[i]=0; }


  // begin main message passing loop
  for (i=1; i<size; i++) {
      //operate on temp buffer with received message buffer and local buffer
      //if i==1, then we don't need to operate on temp array. send local copies
      if (i==1) {
          //receive/send message
          //1) prepost receive
          //2) send mesage
          //3) wait for it to be done
          MPI_Irecv(recv, count, MPI_DOUBLE, right, 0, MPI_COMM_WORLD, &request);
          MPI_Send(sbuf_d, count, MPI_DOUBLE, left, 0, MPI_COMM_WORLD);
          MPI_Wait(&request, &status);
      }
      else if (i>1) {
          MPI_Op_Call(sbuf_d, recv, temp, count, op);
          MPI_Irecv(recv, count, MPI_DOUBLE, right, 0, MPI_COMM_WORLD, &request);  
          MPI_Send(temp, count, MPI_DOUBLE, left, 0, MPI_COMM_WORLD);              
          MPI_Wait(&request, &status);                                             
      }
  }
  MPI_Op_Call(sbuf_d, recv, rbuf_d, count, op);

  // clean up
  free(temp);
  free(recv);
  return 0;
}

/*
 * Buffer operation
 *
 * b1: buffer 1
 * b2: buffer 2
 * res: result buffer
 * n: number of elements to operate on
 * op: the MPI_Op we want 
 */

void MPI_Op_Call(double *b1, double *b2, double *res, int n, MPI_Op op)
{
  int i;
  if (op == MPI_SUM) { 
      for (i=0; i<n; i++) res[i]=b1[i]+b2[i];
  } else if (op == MPI_MAX) {
      for (i=0; i<n; i++) res[i]=(b1[i]>b2[i]?b1[i]:b2[i]);
  } else if (op == MPI_MIN) {
      for (i=0; i<n; i++) res[i]=(b1[i]<b2[i]?b1[i]:b2[i]);
  } else {
  }
}

int modulo(int number, int mod)
{
    int res = number%mod;
    if (res<0) res+=mod;
    return res;
}
